/// ===========================================================================
/// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
/// KIND, WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
/// PURPOSE.
/// ===========================================================================
/// 
/// Project:        MOSS Faceted Search
/// Author:         Leonid Lyublinski (leonidly@microsoft.com)
/// Company:        Microsoft Services
/// Date:           05/04/2007  Version:        1.0
///
/// ===========================================================================

using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Serialization;
using System.Data;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Search.Query;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Portal.ExtendedSearch.Common;

namespace Microsoft.SharePoint.Portal.ExtendedSearch.WebControls
{
    /// <summary>
    /// Custom search bread crumbs web part.
    /// The web part bound to the querystring in and out.
    /// </summary>
    [System.Web.AspNetHostingPermission(System.Security.Permissions.SecurityAction.Demand, Level = System.Web.AspNetHostingPermissionLevel.Minimal)]
    [System.Web.AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = System.Web.AspNetHostingPermissionLevel.Minimal)]
    [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.Demand, Execution = true)]
    //[Microsoft.SharePoint.Security.SharePointPermission(System.Security.Permissions.SecurityAction.Demand, ObjectModel = true)]
    //[System.Security.Permissions.RegistryPermission(System.Security.Permissions.SecurityAction.Demand, Unrestricted = true)]
    //[Microsoft.SharePoint.Security.WebPartPermission(System.Security.Permissions.SecurityAction.Demand, Connections = true)]
    //[ToolboxData("<{0}:SearchBreadCrumbs runat=server></{0}:SearchBreadCrumbs>")]
    [XmlRoot(Namespace = "Microsoft.SharePoint.Portal.ExtendedSearch")]
    public class SearchBreadCrumbs : SearchQueryWebPart
    {
        #region Constants

        #endregion

        #region Fields
        private string _scope;
        private string _k;
        private int _start;
        private string _v;
        private NameValueCollection _properties;

        private string _headerCss; // = "ms-bold";
        private string _facetCss; // = "ms-bold";
        private string _separator; // = " + ";
        private string _facetContainerCss; // = "ms-quicklaunch";
        private string _containerCss; // = "ms-quicklaunch";
        private int _repeatColumns;
        private short _cropMax;

        private string _crumbCss; // = "ms-consoleqaback";
        private string _crumbHoverCss; // = "ms-consoleqabackhover";
        private bool _connected;
        private DataTable _crumbTable;
        private XmlNode _xmlColumns;
        private SearchQuery _query;
        #endregion

        #region Properties

        #region CSS Classes
        /// <summary>
        /// The CSS class for the facet.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_CRUMBCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_CRUMBCSS_DESCRIPTION)]
        public string CrumbCss
        {
            get { return _crumbCss; }
            set { _crumbCss = value; }
        }

        /// <summary>
        /// The CSS class for the facet.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_CRUMBHOVERCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_CRUMBHOVERCSS_DESCRIPTION)]
        public string CrumbHoverCss
        {
            get { return _crumbHoverCss; }
            set { _crumbHoverCss = value; }
        }

        /// <summary>
        /// The CSS class for the face datalist.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_CONTAINERCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_CONTAINERCSS_DESCRIPTION)]
        public string ContainerCss
        {
            get
            {
                return _containerCss;
            }
            set
            {
                _containerCss = value;
            }
        }

        /// <summary>
        /// The CSS class for the face value.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_HEADERCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_HEADERCSS_DESCRIPTION)]
        public string HeaderCss
        {
            get
            {
                return _headerCss;
            }
            set
            {
                _headerCss = value;
            }
        }

        /// <summary>
        /// The CSS class for the face datalist.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_FACETCONTAINERCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_FACETCONTAINERCSS_DESCRIPTION)]
        public string FacetContainerCss
        {
            get
            {
                return _facetContainerCss;
            }
            set
            {
                _facetContainerCss = value;
            }
        }

        /// <summary>
        /// The CSS class for the face name.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_CSS)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_FACETCSS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_FACETCSS_DESCRIPTION)]
        public string FacetCss
        {
            get
            {
                return _facetCss;
            }
            set
            {
                _facetCss = value;
            }
        }
        #endregion

        #region Visual Settings
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_VISUAL)]
        [PersistenceMode(PersistenceMode.Attribute)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_SEARCHFACETS_CROPMAX_FRIENDLYNAME)]
        [WebDescription(Constants.WP_SEARCHFACETS_CROPMAX_DESCRIPTION)]
        public short CropMax
        {
            get
            {
                return _cropMax;
            }
            set
            {
                _cropMax = value;
            }
        }/// <summary>
        /// The Number of table columns.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_VISUAL)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_REPEATCOLUMNS_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_REPEATCOLUMNS_DESCRIPTION)]
        public int RepeatColumns
        {
            get
            {
                return _repeatColumns;
            }
            set
            {
                _repeatColumns = value;
            }
        }

        /// <summary>
        /// A text used as a separator between bread crumbs.
        /// </summary>
        [WebBrowsable(true)]
        [Category(Constants.WP_CATEGORYNAME_VISUAL)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Personalizable(PersonalizationScope.Shared)]
        [WebDisplayName(Constants.WP_BREADCRUMBS_SEPARATOR_FRIENDLYNAME)]
        [WebDescription(Constants.WP_BREADCRUMBS_SEPARATOR_DESCRIPTION)]
        public string Separator
        {
            get
            {
                return _separator;
            }
            set
            {
                _separator = value;
            }
        }
        #endregion
        #endregion

        #region Delegates
        /// <summary>
        /// Constructs the Controls collection of the webpart. 
        /// </summary _
        protected override void CreateChildControls()
        {
            base.CreateChildControls();
            try
            {
                if (!Query.IsEmpty())
                {
                    _query = Query;
                    _k = _query.Keywords;
                    _scope = _query.Scope;
                    _start = _query.Start;
                    _v = _query.View;
                    _properties = _query.Properties;
                }
                else if (Parent != null && Parent.GetType().FullName == "Microsoft.SharePoint.WebPartPages.WebPartPreview")
                {
                    // fake something for WP preview only
                    _query = new SearchQuery();
                    _query.Keywords = _k = "discount";
                    _query.Properties = new NameValueCollection(3);
                    _query.Properties.Add("Manufacturer", "Sony#;#Panasonic");
                    _query.Properties.Add("Category", "Electronics");
                    _query.Properties.Add("In stock", "Yes");
                    _properties = _query.Properties;
                }

                if (!_query.IsEmpty())
                {
                    InitCrumbTable();
                    PopulateCrumbTable();

                    Label lSearch = new Label();
                    lSearch.EnableViewState = false;
                    lSearch.CssClass = _headerCss;
                    lSearch.Text = (string.IsNullOrEmpty(_k)) ? "" : Resource.SEARCH_BREADCRUMBS_WEBPART_PREFIX +
                        HttpUtility.HtmlEncode(_k);

                    DataList list = new DataList();
                    list.ID = "CrumbTable";
                    list.CellPadding = 2;
                    list.CellSpacing = 0;
                    list.RepeatDirection = RepeatDirection.Horizontal;
                    list.RepeatColumns = _repeatColumns;
                    list.ItemStyle.VerticalAlign = VerticalAlign.Top;
                    list.ItemStyle.Width = Unit.Pixel(150);
                    list.HeaderStyle.CssClass = _facetContainerCss;
                    list.ItemTemplate = new CrumbTemplate(ListItemType.Item, _query, _facetCss, _facetContainerCss, _cropMax);
                    if (!string.IsNullOrEmpty(_separator)) list.SeparatorTemplate = new SeparatorTemplate(ListItemType.Separator, _facetCss, _separator);
                    list.ItemDataBound += new DataListItemEventHandler(CrumbTable_ItemDataBound);
                    list.DataSource = _crumbTable;
                    list.DataBind();

                    HtmlTable table = BreadCrumbsTable(lSearch, list);

                    Controls.Add(table);
                }
            }
            catch (Exception ex)
            {
                Utility.HandleException(ex, true, Resource.ErrorMessageBreadCrumbs, Controls);
            }
        }

        protected void CrumbTable_ItemDataBound(object sender, DataListItemEventArgs e)
        {
            DataListItem item = e.Item;
            DataRow row = ((DataRowView)item.DataItem).Row;
            string facetName = row["FacetName"].ToString();
            string facetDisplayName = row["FacetDisplayName"].ToString();
            //item.Controls.Add(new LiteralControl(facetDisplayName));
            NameValueCollection facetValueCollection = (NameValueCollection) row["FacetValueCollection"];
            DataTable crumbFacetTable = BuildCrumbFacetTable();
            foreach (string key in facetValueCollection)
            {
                crumbFacetTable.Rows.Add(new string[2] { key, facetValueCollection[key] });
            }

            DataList crumbFacetsList = new DataList();
            crumbFacetsList.ID = "CrumbFacetsTable";
            crumbFacetsList.BorderWidth = 1;
            crumbFacetsList.Width = Unit.Percentage(100);
            crumbFacetsList.RepeatDirection = RepeatDirection.Vertical;
            crumbFacetsList.ItemTemplate = new CrumbFacetTemplate(ListItemType.Item, _query, _crumbCss, facetName, _crumbCss, _crumbHoverCss, _cropMax);
            crumbFacetsList.DataSource = crumbFacetTable;
            crumbFacetsList.DataBind();
            item.Controls.Add(crumbFacetsList);
        }

        #endregion

        #region Private Methods
        private HtmlTable BreadCrumbsTable(Control keyword, Control metas)
        {
            HtmlTable table = new HtmlTable();
            table.Attributes.Add("class", _containerCss);
            table.CellSpacing = 0;
            table.Border = 0;

            HtmlTableRow rowKeyword = new HtmlTableRow();
            HtmlTableCell cellKeyword = new HtmlTableCell();
            cellKeyword.Controls.Add(keyword);
            cellKeyword.Attributes.Add("class", "ms-sctabrx");
            cellKeyword.NoWrap = true;
            rowKeyword.Cells.Add(cellKeyword);

            if (metas.HasControls())
            {
                HtmlImage img = new HtmlImage();
                img.Src = "/_layouts/images/filteroff.gif";
                img.Border = 0;

                HyperLink resetSearch = new HyperLink();
                resetSearch.NavigateUrl = CleanSearch();
                resetSearch.Text = Resource.SEARCH_BREADCRUMBS_WEBPART_RESET_ALL_BUTTON;
                if (!string.IsNullOrEmpty(_k)) cellKeyword.Controls.Add(new LiteralControl("&nbsp;|&nbsp;"));
                cellKeyword.Controls.Add(resetSearch);

                HyperLink resetFacets = new HyperLink();
                resetFacets.NavigateUrl = CleanUrl();
                resetFacets.Text = Resource.SEARCH_BREADCRUMBS_WEBPART_RESET_BUTTON;
                cellKeyword.Controls.Add(new LiteralControl("&nbsp;|&nbsp;"));
                cellKeyword.Controls.Add(resetFacets);
            }
            if (!string.IsNullOrEmpty(_k))
            {
                HyperLink resetKeyword = new HyperLink();
                resetKeyword.NavigateUrl = CleanKeyword();
                resetKeyword.Text = Resource.SEARCH_BREADCRUMBS_WEBPART_RESET_KEYWORD_BUTTON;
                cellKeyword.Controls.Add(new LiteralControl("&nbsp;|&nbsp;"));
                cellKeyword.Controls.Add(resetKeyword);
            }

            table.Rows.Add(rowKeyword);
            //
            if (metas.HasControls())
            {
                HtmlTableRow rowMetas = new HtmlTableRow();
                HtmlTableCell cellMetas = new HtmlTableCell();
                cellMetas.NoWrap = true;
                cellMetas.Width = "100%";
                cellMetas.Align = "left";
                cellMetas.VAlign = "top";
                cellMetas.Controls.Add(metas);
                rowMetas.Cells.Add(cellMetas);
                table.Rows.Add(rowMetas);
            }

            return table;
        }

        private string CleanUrl()
        {
            SearchQuery query = _query.Clone();
            query.Properties.Clear();
            string qs = Utility.BuildQueryString(query);
            string qsEncoded = string.Format("{0}?{1}", HttpContext.Current.Request.Url.AbsolutePath, qs);
            return qsEncoded;
        }

        private string CleanKeyword()
        {
            SearchQuery query = _query.Clone();
            query.Keywords = string.Empty;
            string qs = Utility.BuildQueryString(query);
            string qsEncoded = string.Format("{0}?{1}", HttpContext.Current.Request.Url.AbsolutePath, qs);
            return qsEncoded;
        }

        private string CleanSearch()
        {
            SearchQuery query = _query.Clone();
            query.Properties.Clear();
            query.Keywords = "";
            string qs = Utility.BuildQueryString(query);
            string qsEncoded = string.Format("{0}?{1}", HttpContext.Current.Request.Url.AbsolutePath, qs);
            return qsEncoded;
        }

        private void InitCrumbTable()
        {
            _crumbTable = new DataTable("CrumbTable");
            _crumbTable.Columns.Add("FacetName", typeof(string));
            _crumbTable.Columns.Add("FacetDisplayName", typeof(string));
            _crumbTable.Columns.Add("FacetValueCollection", typeof(NameValueCollection));
        }

        private DataTable BuildCrumbFacetTable(){
            DataTable table = new DataTable();
            table.Columns.Add("FacetValue", typeof(string));
            table.Columns.Add("FacetDisplayValue", typeof(string));
            return table;
        }

        private void PopulateCrumbTable()
        {
            foreach (string prop in _properties)
            {
                DataRow row = _crumbTable.NewRow();
                row["FacetName"] = prop;
                row["FacetDisplayName"] = (_connected && !string.IsNullOrEmpty(GetFacetDisplayName(prop))) ? GetFacetDisplayName(prop) : prop;
                string facetValue = _properties[prop];
                NameValueCollection facetValueCollection = new NameValueCollection();
                // provision for multiple values for the facet
                if (facetValue.Contains(Splitter.Delimiter))
                {
                    string[] alTmp = facetValue.Split(Splitter.UniDelimiter, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string propValue in alTmp)
                    {
                        facetValueCollection.Add(propValue, (_connected && !string.IsNullOrEmpty(GetFacetDisplayValue(prop, propValue))) ? GetFacetDisplayValue(prop, propValue) : propValue);
                    }
                }
                else
                {
                    facetValueCollection.Add(facetValue, (_connected && !string.IsNullOrEmpty(GetFacetDisplayValue(prop, facetValue))) ? GetFacetDisplayValue(prop, facetValue) : facetValue);
                }
                row["FacetValueCollection"] = facetValueCollection;
                _crumbTable.Rows.Add(row);
            }
        }


        private string GetFacetDisplayName(string facetName)
        {
            try
            {
                string xpath = string.Format("//Column[@Name='{0}']/@DisplayName", facetName);
                XmlNode node = _xmlColumns.SelectSingleNode(xpath);

                if (node == null) return null;
                else return node.Value;
            }
            catch
            {
                return null;
            }
            return facetName;
        }
        private string GetFacetDisplayValue(string facetName, string facetValue)
        {
            try
            {
                string xpath = string.Format("//Column[@Name='{0}']/Mappings/Mapping[(contains(@Match, '%') and starts-with('{1}', substring-before(@Match, '%'))) or (@Match = '{1}')]/@DisplayName", facetName, facetValue);
                XmlNode node = _xmlColumns.SelectSingleNode(xpath);

                if (node == null) return null;
                else return node.Value;
            }
            catch
            {
                return null;
            }
        }

        #endregion

        [ConnectionConsumer("Facet Settings", "FacetSettings", AllowsMultipleConnections = false)]
        public void GetFacetSettings(XmlNode facets)
        {
            _xmlColumns = facets;
            _connected = (facets != null);
        }
    }
}
